למדו כיצד לממש Error Boundaries בריאקט באמצעות Hooks כדי לטפל בחן בשגיאות טעינת משאבים, ולשפר את חווית המשתמש ויציבות האפליקציה.
טעינת משאבים איתנה בריאקט: שליטה ב-Error Boundaries באמצעות Hooks
באפליקציות ווב מודרניות, טעינת משאבים באופן אסינכרוני היא פרקטיקה נפוצה. בין אם מדובר באחזור נתונים מ-API, טעינת תמונות או ייבוא מודולים, טיפול בשגיאות פוטנציאליות במהלך טעינת המשאבים הוא חיוני לחוויית משתמש חלקה. Error Boundaries בריאקט מספקים מנגנון לתפיסת שגיאות JavaScript בכל מקום בעץ הקומפוננטות הצאצאות שלהם, לתיעוד שגיאות אלו, ולהצגת UI חלופי במקום קריסה של כל האפליקציה. מאמר זה בוחן כיצד להשתמש ב-Error Boundaries ביעילות בשילוב עם React Hooks לניהול שגיאות בטעינת משאבים.
הבנת Error Boundaries
לפני ריאקט 16, שגיאות JavaScript שלא טופלו במהלך רינדור קומפוננטה היו עלולות להשחית את המצב הפנימי של ריאקט ולגרום לשגיאות סתומות ברינדורים הבאים. Error Boundaries מטפלים בבעיה זו על ידי כך שהם פועלים כבלוקים שתופסים את כל השגיאות המתרחשות בקומפוננטות הצאצאות שלהם. הם קומפוננטות ריאקט המממשות אחת משתי מתודות מחזור החיים הבאות, או את שתיהן:
static getDerivedStateFromError(error): מתודה סטטית זו נקראת לאחר ששגיאה נזרקה על ידי קומפוננטה צאצאית. היא מקבלת את השגיאה שנזרקה כארגומנט ומחזירה ערך לעדכון ה-state של הקומפוננטה.componentDidCatch(error, info): מתודת מחזור חיים זו נקראת לאחר ששגיאה נזרקה על ידי קומפוננטה צאצאית. היא מקבלת את השגיאה שנזרקה כארגומנט, וכן אובייקט המכיל מידע על איזו קומפוננטה זרקה את השגיאה. ניתן להשתמש בה לתיעוד מידע על השגיאה.
חשוב לציין, Error Boundaries תופסים שגיאות רק בשלב ה-רינדור, במתודות מחזור חיים, ובבנאים של כל העץ שמתחתיהם. הם אינם תופסים שגיאות עבור:
- מטפלי אירועים (event handlers) (למדו עוד בחלק הבא)
- קוד אסינכרוני (למשל, קריאות חוזרות של
setTimeoutאוrequestAnimationFrame) - רינדור בצד השרת (Server-side rendering)
- שגיאות שנזרקות ב-Error Boundary עצמו (ולא בצאצאיו)
Error Boundaries ו-React Hooks: שילוב עוצמתי
בעוד שקומפוננטות מבוססות מחלקה (class components) שימשו באופן מסורתי למימוש Error Boundaries, React Hooks מציעים גישה תמציתית ופונקציונלית יותר. אנו יכולים ליצור hook רב-פעמי בשם useErrorBoundary שמכמסל את לוגיקת הטיפול בשגיאות ומספק דרך נוחה לעטוף קומפוננטות שעלולות לזרוק שגיאות במהלך טעינת משאבים.
יצירת Hook מותאם אישית useErrorBoundary
הנה דוגמה ל-hook useErrorBoundary:
import { useState, useCallback } from 'react';
function useErrorBoundary() {
const [error, setError] = useState(null);
const resetError = useCallback(() => {
setError(null);
}, []);
const captureError = useCallback((e) => {
setError(e);
}, []);
const ErrorBoundary = useCallback(({ children, fallback }) => {
if (error) {
return fallback ? fallback : An error occurred: {error.message || String(error)};
}
return children;
}, [error]);
return { ErrorBoundary, captureError, error, resetError };
}
export default useErrorBoundary;
הסבר:
useState: אנו משתמשים ב-useStateכדי לנהל את מצב השגיאה. בתחילה, הוא מגדיר את השגיאה ל-null.useCallback: אנו משתמשים ב-useCallbackכדי לבצע ממואיזציה (memoize) לפונקציותresetErrorו-captureError. זוהי פרקטיקה טובה למניעת רינדורים מחדש מיותרים אם פונקציות אלו מועברות כ-props.- קומפוננטת
ErrorBoundary: זוהי קומפוננטה פונקציונלית שנוצרה עםuseCallbackשמקבלתchildrenו-prop אופציונלי שלfallback. אם קיימת שגיאה ב-state, היא מרנדרת את קומפוננטת ה-fallbackשסופקה או הודעת שגיאה ברירת מחדל. אחרת, היא מרנדרת את ה-children. זה פועל כ-Error Boundary שלנו. מערך התלויות `[error]` מבטיח שהיא תתעדכן מחדש כאשר מצב ה-`error` משתנה. - פונקציית
captureError: פונקציה זו משמשת להגדרת מצב השגיאה. תקראו לה בתוך בלוקtry...catchבעת טעינת משאבים. - פונקציית
resetError: פונקציה זו מנקה את מצב השגיאה, ומאפשרת לקומפוננטה לרנדר מחדש את צאצאיה (ובכך אולי לנסות שוב את טעינת המשאבים).
מימוש טעינת משאבים עם טיפול בשגיאות
כעת, בואו נראה כיצד להשתמש ב-hook זה לטיפול בשגיאות טעינת משאבים. נבחן קומפוננטה שמביאה נתוני משתמש מ-API:
import React, { useState, useEffect } from 'react';
import useErrorBoundary from './useErrorBoundary';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const { ErrorBoundary, captureError, error, resetError } = useErrorBoundary();
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (e) {
captureError(e);
}
};
fetchData();
}, [userId, captureError]);
if (error) {
return (
Failed to load user data. {user.name}
Email: {user.email}
{/* Other user details */}הסבר:
- אנו מייבאים את ה-hook
useErrorBoundary. - אנו קוראים ל-hook כדי לקבל את קומפוננטת
ErrorBoundary, הפונקציהcaptureError, מצב ה-error, והפונקציהresetError. - בתוך ה-hook
useEffect, אנו עוטפים את קריאת ה-API בבלוקtry...catch. - אם מתרחשת שגיאה במהלך קריאת ה-API, אנו קוראים ל-
captureError(e)כדי להגדיר את מצב השגיאה. - אם מצב ה-
errorמוגדר, אנו מרנדרים את קומפוננטת ה-ErrorBoundary. אנו מספקים propfallbackמותאם אישית המציג הודעת שגיאה וכפתור "נסה שוב". לחיצה על הכפתור קוראת ל-resetErrorכדי לנקות את מצב השגיאה, מה שגורם לרינדור מחדש וניסיון נוסף להביא את הנתונים. - אם לא התרחשה שגיאה ונתוני המשתמש נטענו, אנו מרנדרים את פרטי פרופיל המשתמש.
טיפול בסוגים שונים של שגיאות טעינת משאבים
סוגים שונים של שגיאות טעינת משאבים עשויים לדרוש אסטרטגיות טיפול שונות. הנה כמה תרחישים נפוצים וכיצד לטפל בהם:
שגיאות רשת
שגיאות רשת מתרחשות כאשר הלקוח אינו מסוגל להתחבר לשרת (למשל, עקב הפסקת רשת או השבתת שרת). הדוגמה לעיל כבר מטפלת בשגיאות רשת בסיסיות באמצעות `response.ok`. ייתכן שתרצו להוסיף זיהוי שגיאות מתוחכם יותר, למשל:
//Inside the fetchData function
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
// Consider adding specific error code handling
if (response.status === 404) {
throw new Error("User not found");
} else if (response.status >= 500) {
throw new Error("Server error. Please try again later.");
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
}
const data = await response.json();
setUser(data);
} catch (error) {
if (error.message === 'Failed to fetch') {
// Likely a network error
captureError(new Error('Network error. Please check your internet connection.'));
} else {
captureError(error);
}
}
במקרה זה, תוכלו להציג הודעה למשתמש המציינת שיש בעיית קישוריות לרשת ולהציע לו לבדוק את חיבור האינטרנט שלו.
שגיאות API
שגיאות API מתרחשות כאשר השרת מחזיר תגובת שגיאה (למשל, 400 Bad Request או 500 Internal Server Error). כפי שמוצג לעיל, ניתן לבדוק את `response.status` ולטפל בשגיאות אלו בהתאם.
שגיאות ניתוח נתונים (Parsing)
שגיאות ניתוח נתונים מתרחשות כאשר התגובה מהשרת אינה בפורמט הצפוי ולא ניתן לנתח אותה (למשל, JSON לא חוקי). ניתן לטפל בשגיאות אלו על ידי עטיפת קריאת response.json() בבלוק try...catch:
//Inside the fetchData function
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (error) {
if (error instanceof SyntaxError) {
captureError(new Error('Failed to parse data from server.'));
} else {
captureError(error);
}
}
שגיאות טעינת תמונה
לטעינת תמונות, ניתן להשתמש במטפל האירועים onError על תג ה-<img>:
function MyImage({ src, alt }) {
const { ErrorBoundary, captureError } = useErrorBoundary();
const [imageLoaded, setImageLoaded] = useState(false);
const handleImageLoad = () => {
setImageLoaded(true);
};
const handleImageError = (e) => {
captureError(new Error(`Failed to load image: ${src}`));
};
return (
Failed to load image.